From 7d6b5fcc8cfb30d2054aafb42bd4b43b6f112870 Mon Sep 17 00:00:00 2001 From: tsteven4 <13596209+tsteven4@users.noreply.github.com> Date: Thu, 4 Mar 2021 16:13:42 -0700 Subject: [PATCH] modernize nmea reader (#700) --- nmea.cc | 384 +++++++++++------------- nmea.h | 55 ++-- reference/track/addmultipledays.csv | 29 ++ reference/track/addmultipledaysbkw.nmea | 29 ++ reference/track/addmultipledaysfwd.nmea | 28 ++ reference/track/addmultipledaysmid.nmea | 29 ++ testo.d/nmea.test | 8 + xmldoc/formats/options/nmea-pause.xml | 2 +- 8 files changed, 334 insertions(+), 230 deletions(-) create mode 100644 reference/track/addmultipledays.csv create mode 100644 reference/track/addmultipledaysbkw.nmea create mode 100644 reference/track/addmultipledaysfwd.nmea create mode 100644 reference/track/addmultipledaysmid.nmea diff --git a/nmea.cc b/nmea.cc index 6be06f149..a5eee484e 100644 --- a/nmea.cc +++ b/nmea.cc @@ -20,26 +20,25 @@ */ -#include // for isprint -#include // for fabs, lround -#include // for snprintf, sscanf, NULL, fprintf, fputc, stderr -#include // for atoi, atof, strtod -#include // for strncmp, memset, strlen, strchr, strstr, strrchr -#include // for time_t, gmtime -#include // for operator!=, reverse_iterator - -#include // for QByteArray -#include // for QChar, operator==, operator!= -#include // for QCharRef -#include // for QDateTime -#include // for QDebug -#include // for QList -#include // for QString, QString::KeepEmptyParts -#include // for QStringList -#include // for hex -#include // for QThread -#include // for QTime -#include // for qPrintable, foreach +#include // for isprint +#include // for fabs +#include // for snprintf, sscanf, fprintf, fputc, stderr +#include // for atoi, atof, strtod +#include // for strncmp, strchr, strlen, strstr, memset, strrchr +#include // for operator!=, reverse_iterator + +#include // for QByteArray +#include // for QChar, operator==, operator!= +#include // for QDateTime +#include // for QDebug +#include // for QList +#include // for QString, QString::KeepEmptyParts +#include // for QStringList +#include // for hex +#include // for QThread +#include // for QTime +#include // for UTC +#include // for qPrintable, foreach #include "defs.h" #include "nmea.h" @@ -49,7 +48,6 @@ #include "jeeps/gpsmath.h" // for GPS_Lookup_Datum_Index, GPS_Math_Known_Datum_To_WGS84_M #include "src/core/datetime.h" // for DateTime #include "src/core/logging.h" // for Warning -#include "strptime.h" // for strptime /********************************************************** @@ -223,9 +221,8 @@ NmeaFormat::rd_init(const QString& fname) { curr_waypt = nullptr; last_waypt = nullptr; - last_time = -1; datum = DATUM_WGS84; - had_checksum = 0; + had_checksum = false; CHECK_BOOL(opt_gprmc); CHECK_BOOL(opt_gpgga); @@ -291,12 +288,12 @@ NmeaFormat::wr_init(const QString& fname) file_out = gbfopen(fname, append_output ? "a+" : "w+", MYNAME); - sleepus = -1; + sleepms = -1; if (opt_sleep) { if (*opt_sleep) { - sleepus = 1e6 * atof(opt_sleep); + sleepms = 1e3 * atof(opt_sleep); } else { - sleepus = -1; + sleepms = -1; } } @@ -318,32 +315,50 @@ NmeaFormat::wr_deinit() } void -NmeaFormat::nmea_set_waypoint_time(Waypoint* wpt, struct tm* time, double fsec) +NmeaFormat::nmea_set_waypoint_time(Waypoint* wpt, QDateTime* prev, const QDate& date, const QTime& time) { - if (time->tm_year == 0) { - wpt->SetCreationTime(((((time_t)time->tm_hour * 60) + time->tm_min) * 60) + time->tm_sec, lround(1000.0 * fsec)); - if (wpt->wpt_flags.fmt_use == 0) { - wpt->wpt_flags.fmt_use = 1; - without_date++; + if (date.isValid()) { + wpt->SetCreationTime(QDateTime(date, time, Qt::UTC)); + if (wpt->wpt_flags.fmt_use != 0) { + wpt->wpt_flags.fmt_use = 0; + without_date--; + } + *prev = wpt->GetCreationTime(); + } else if (prev->date().isValid()) { + wpt->SetCreationTime(QDateTime(prev->date(), time, Qt::UTC)); + if (*prev > wpt->creation_time) { + /* go over midnight ? */ + wpt->creation_time = wpt->creation_time.addDays(1); } - } else { - wpt->SetCreationTime(mkgmtime(time), lround(1000.0 * fsec)); if (wpt->wpt_flags.fmt_use != 0) { wpt->wpt_flags.fmt_use = 0; without_date--; } + *prev = wpt->GetCreationTime(); + } else { + wpt->SetCreationTime(QDateTime(QDate(), time, Qt::UTC)); + if (wpt->wpt_flags.fmt_use == 0) { + wpt->wpt_flags.fmt_use = 1; + without_date++; + } } } +QTime NmeaFormat::nmea_parse_hms(const QString& str) +{ + QString format = str.contains('.')? "hhmmss.zzz" : "hhmmss"; + return QTime::fromString(str, format); +} + void -NmeaFormat::gpgll_parse(char* ibuf) +NmeaFormat::gpgll_parse(const QString& ibuf) { if (trk_head == nullptr) { trk_head = new route_head; track_add_head(trk_head); } - QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts); + const QStringList fields = ibuf.split(',', QString::KeepEmptyParts); double latdeg = 0; if (fields.size() > 1) latdeg = fields[1].toDouble(); @@ -353,8 +368,8 @@ NmeaFormat::gpgll_parse(char* ibuf) if (fields.size() > 3) lngdeg = fields[3].toDouble(); QChar lngdir = 'E'; if (fields.size() > 4) lngdir = fields[4][0]; - double hmsd = 0; - if (fields.size() > 5) hmsd = fields[5].toDouble(); + QTime hms; + if (fields.size() > 5) hms = nmea_parse_hms(fields[5]); bool valid = false; if (fields.size() > 6) valid = fields[6].startsWith('A'); @@ -362,19 +377,11 @@ NmeaFormat::gpgll_parse(char* ibuf) return; } - int hms = (int) hmsd; last_read_time = hms; - double fsec = hmsd - hms; - - tm.tm_sec = hms % 100; - hms = hms / 100; - tm.tm_min = hms % 100; - hms = hms / 100; - tm.tm_hour = hms % 100; Waypoint* waypt = nmea_new_wpt(); - nmea_set_waypoint_time(waypt, &tm, fsec); + nmea_set_waypoint_time(waypt, &prev_datetime, QDate(), hms); if (latdir == 'S') { latdeg = -latdeg; @@ -391,16 +398,16 @@ NmeaFormat::gpgll_parse(char* ibuf) } void -NmeaFormat::gpgga_parse(char* ibuf) +NmeaFormat::gpgga_parse(const QString& ibuf) { if (trk_head == nullptr) { trk_head = new route_head; track_add_head(trk_head); } - QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts); - double hms = 0; - if (fields.size() > 1) hms = fields[1].toDouble(); + const QStringList fields = ibuf.split(',', QString::KeepEmptyParts); + QTime hms; + if (fields.size() > 1) hms = nmea_parse_hms(fields[1]); double latdeg = 0; if (fields.size() > 2) latdeg = fields[2].toDouble(); QChar latdir = 'N'; @@ -413,8 +420,8 @@ NmeaFormat::gpgga_parse(char* ibuf) if (fields.size() > 6) fix = fields[6].toInt(); int nsats = 0; if (fields.size() > 7) nsats = fields[7].toInt(); - double hdop = 0; - if (fields.size() > 8) hdop = fields[8].toDouble(); + float hdop = 0; + if (fields.size() > 8) hdop = fields[8].toFloat(); double alt = unknown_alt; if (fields.size() > 9) alt = fields[9].toDouble(); QChar altunits ='M'; @@ -436,17 +443,10 @@ NmeaFormat::gpgga_parse(char* ibuf) } last_read_time = hms; - double fsec = hms - (int)hms; - - tm.tm_sec = (long) hms % 100; - hms = hms / 100; - tm.tm_min = (long) hms % 100; - hms = hms / 100; - tm.tm_hour = (long) hms % 100; Waypoint* waypt = nmea_new_wpt(); - nmea_set_waypoint_time(waypt, &tm, fsec); + nmea_set_waypoint_time(waypt, &prev_datetime, QDate(), hms); if (latdir == 'S') { latdeg = -latdeg; @@ -488,16 +488,16 @@ NmeaFormat::gpgga_parse(char* ibuf) } void -NmeaFormat::gprmc_parse(char* ibuf) +NmeaFormat::gprmc_parse(const QString& ibuf) { if (trk_head == nullptr) { trk_head = new route_head; track_add_head(trk_head); } - QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts); - double hms = 0; - if (fields.size() > 1) hms = fields[1].toDouble(); + const QStringList fields = ibuf.split(',', QString::KeepEmptyParts); + QTime hms; + if (fields.size() > 1) hms = nmea_parse_hms(fields[1]); QChar fix = 'V'; // V == "Invalid" if (fields.size() > 2) fix = fields[2][0]; double latdeg = 0; @@ -512,28 +512,18 @@ NmeaFormat::gprmc_parse(char* ibuf) if (fields.size() > 7) speed = fields[7].toDouble(); double course = 0; if (fields.size() > 8) course = fields[8].toDouble(); - int dmy = 0; - if (fields.size() > 9) dmy = fields[9].toDouble(); - + QDate dmy; + if (fields.size() > 9) { + QString datestr(fields[9]); + datestr.insert(4, "20"); + dmy = QDate::fromString(datestr, "ddMMyyyy"); + } if (fix != 'A') { /* ignore this fix - it is invalid */ return; } last_read_time = hms; - double fsec = hms - (int)hms; - - tm.tm_sec = (long) hms % 100; - hms = hms / 100; - tm.tm_min = (long) hms % 100; - hms = hms / 100; - tm.tm_hour = (long) hms % 100; - - tm.tm_year = dmy % 100 + 100; - dmy = dmy / 100; - tm.tm_mon = dmy % 100 - 1; - dmy = dmy / 100; - tm.tm_mday = dmy; if (posn_type == gpgga) { /* capture useful data update and exit */ @@ -546,7 +536,7 @@ NmeaFormat::gprmc_parse(char* ibuf) } /* The change of date wasn't recorded when * going from 235959 to 000000. */ - nmea_set_waypoint_time(curr_waypt, &tm, fsec); + nmea_set_waypoint_time(curr_waypt, &prev_datetime, dmy, hms); } /* This point is both a waypoint and a trackpoint. */ if (amod_waypoint) { @@ -561,7 +551,7 @@ NmeaFormat::gprmc_parse(char* ibuf) WAYPT_SET(waypt, speed, KNOTS_TO_MPS(speed)); WAYPT_SET(waypt, course, course); - nmea_set_waypoint_time(waypt, &tm, fsec); + nmea_set_waypoint_time(waypt, &prev_datetime, dmy, hms); if (latdir == 'S') { latdeg = -latdeg; @@ -584,13 +574,9 @@ NmeaFormat::gprmc_parse(char* ibuf) } void -NmeaFormat::gpwpl_parse(char* ibuf) +NmeaFormat::gpwpl_parse(const QString& ibuf) { - // The last field isn't actually separated by a field separator and - // is a string, so we brutally whack the checksum (trailing *NN). - QString qibuf = QString(ibuf); - qibuf.truncate(qibuf.lastIndexOf('*')); - QStringList fields = qibuf.split(",", QString::KeepEmptyParts); + const QStringList fields = ibuf.split(',', QString::KeepEmptyParts); double latdeg = 0; if (fields.size() > 1) latdeg = fields[1].toDouble(); @@ -620,21 +606,18 @@ NmeaFormat::gpwpl_parse(char* ibuf) } void -NmeaFormat::gpzda_parse(char* ibuf) +NmeaFormat::gpzda_parse(const QString& ibuf) { - double hms; - int dd, mm, yy, lclhrs, lclmins; - - sscanf(ibuf,"$%*2cZDA,%lf,%d,%d,%d,%d,%d", - &hms, &dd, &mm, &yy, &lclhrs, &lclmins); - tm.tm_sec = (int) hms % 100; - tm.tm_min = (((int) hms - tm.tm_sec) / 100) % 100; - tm.tm_hour = (int) hms / 10000; - tm.tm_mday = dd; - tm.tm_mon = mm - 1; - tm.tm_year = yy - 1900; - // FIXME: why do we do all this and then do nothing with the result? - // This can't have worked. + const QStringList fields = ibuf.split(',', QString::KeepEmptyParts); + if (fields.size() > 4) { + QTime time = nmea_parse_hms(fields[1]); + QString datestr = QString("%1%2%3").arg(fields[2], fields[3], fields[4]); + QDate date = QDate::fromString(datestr, "ddMMyyyy"); + + // The prev_datetime data member might be used by + // nmea_fix_timestamps and nmea_set_waypoint_time. + prev_datetime = QDateTime(date, time, Qt::UTC); + } } // This function has had a hard life. It began as a moderately terrifying @@ -646,12 +629,12 @@ NmeaFormat::gpzda_parse(char* ibuf) // The numbering as per http://aprs.gids.nl/nmea/#gsa was the reference as // the field numbers conveniently match our index. void -NmeaFormat::gpgsa_parse(char* ibuf) const +NmeaFormat::gpgsa_parse(const QString& ibuf) const { int prn[12] = {0}; memset(prn,0xff,sizeof(prn)); - QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts); + const QStringList fields = ibuf.split(',', QString::KeepEmptyParts); int nfields = fields.size(); // 0 = "GPGSA" // 1 = Mode. Ignored @@ -668,11 +651,7 @@ NmeaFormat::gpgsa_parse(char* ibuf) const float pdop = 0, hdop = 0, vdop = 0; if (nfields > 15) pdop = fields[15].toFloat(); if (nfields > 16) hdop = fields[16].toFloat(); - if (nfields > 17) { - // Last one is special. The checksum isn't split out above. - fields[17].chop(3); - vdop = fields[17].toFloat(); - } + if (nfields > 17) vdop = fields[17].toFloat(); if (curr_waypt) { if (curr_waypt->fix!=fix_dgps) { @@ -697,9 +676,9 @@ NmeaFormat::gpgsa_parse(char* ibuf) const } void -NmeaFormat::gpvtg_parse(char* ibuf) const +NmeaFormat::gpvtg_parse(const QString& ibuf) const { - QStringList fields = QString(ibuf).split(",", QString::KeepEmptyParts); + const QStringList fields = ibuf.split(',', QString::KeepEmptyParts); double course = 0; if (fields.size() > 1) course = fields[1].toDouble(); double speed_n = 0; @@ -730,7 +709,7 @@ double NmeaFormat::pcmpt_deg(int d) } void -NmeaFormat::pcmpt_parse(char* ibuf) +NmeaFormat::pcmpt_parse(const char* ibuf) { int i, j1, j2, j3, j4, j5, j6; int lat, lon; @@ -772,18 +751,21 @@ NmeaFormat::pcmpt_parse(char* ibuf) curr_waypt->longitude = pcmpt_deg(lon); curr_waypt->latitude = pcmpt_deg(lat); - tm.tm_sec = (long) hms % 100; + int sec = hms % 100; hms = hms / 100; - tm.tm_min = (long) hms % 100; + int min = hms % 100; hms = hms / 100; - tm.tm_hour = (long) hms % 100; + int hour = hms % 100; + QTime time = QTime(hour, min, sec); - tm.tm_year = dmy % 10000 - 1900; + int year = dmy % 10000; dmy = dmy / 10000; - tm.tm_mon = dmy % 100 - 1; + int mon = dmy % 100; dmy = dmy / 100; - tm.tm_mday = dmy; - nmea_set_waypoint_time(curr_waypt, &tm, 0); + int mday = dmy; + QDate date = QDate(year, mon, mday); + + nmea_set_waypoint_time(curr_waypt, &prev_datetime, date, time); pcmpt_head.prepend(curr_waypt); } else { @@ -812,29 +794,27 @@ NmeaFormat::nmea_fix_timestamps(route_head* track) return; } - if (tm.tm_year == 0) { - Waypoint* prev = nullptr; - + if (!prev_datetime.date().isValid()) { if (optdate == nullptr) { warning(MYNAME ": No date found within track (all points dropped)!\n"); warning(MYNAME ": Please use option \"date\" to preset a valid date for those tracks.\n"); track_del_head(track); return; } - time_t delta_tm = mkgmtime(&opt_tm); + + QDateTime prev = QDateTime(opt_tm, QTime(0, 0), Qt::UTC); foreach (Waypoint* wpt, track->waypoint_list) { - wpt->creation_time = wpt->creation_time.addSecs(delta_tm); - if ((prev != nullptr) && (prev->creation_time > wpt->creation_time)) { + wpt->creation_time.setDate(prev.date()); + if (prev > wpt->creation_time) { /* go over midnight ? */ - delta_tm += SECONDS_PER_DAY; - wpt->creation_time = wpt->creation_time.addSecs(SECONDS_PER_DAY); + wpt->creation_time = wpt->creation_time.addDays(1); } - prev = wpt; + prev = wpt->GetCreationTime(); } } else { - time_t prev = mkgmtime(&tm); + QDateTime prev = prev_datetime; /* go backward through the track and complete timestamps */ @@ -844,70 +824,79 @@ NmeaFormat::nmea_fix_timestamps(route_head* track) if (wpt->wpt_flags.fmt_use != 0) { wpt->wpt_flags.fmt_use = 0; /* reset flag */ - time_t dt = (prev / SECONDS_PER_DAY) * SECONDS_PER_DAY; - wpt->creation_time = wpt->creation_time.addSecs(dt); - if (wpt->creation_time.toTime_t() > prev) { - wpt->creation_time = wpt->creation_time.addSecs(-SECONDS_PER_DAY); + wpt->creation_time.setDate(prev.date()); + if (wpt->creation_time > prev) { + wpt->creation_time = wpt->creation_time.addDays(-1); } } - prev = wpt->GetCreationTime().toTime_t(); + prev = wpt->GetCreationTime(); } } } -int -NmeaFormat::notalkerid_strmatch(const char * s1, const char *sentenceFormatterMnemonicCode) +bool +NmeaFormat::notalkerid_strmatch(const QByteArray& s1, const char *sentenceFormatterMnemonicCode) { /* - * compare leading start of parametric sentence character ('$'), sentence address field, and trailing comma - * to the desired sentence formatter mnemonic code (the 3rd-5th characters of the sentence address field). - * The talker identifier mnemonic(the 1st-2nd characters of the sentence address field) - * is likely "GP" for Global Positioning System (GPS) - * but other talkers like "IN" for Integrated Navigation can emit relevant sentences, - * so we ignore the talker identifier mnemonic. + * compare leading start of parametric sentence character ('$'), sentence + * address field, and trailing comma to the desired sentence formatter mnemonic + * code (the 3rd-5th characters of the sentence address field). The talker + * identifier mnemonic (the 1st-2nd characters of the sentence address field) + * is likely "GP" for Global Positioning System (GPS) but other talkers like + * "IN" for Integrated Navigation can emit relevant sentences, so we ignore the + * talker identifier mnemonic. */ -return strncmp(s1,"$",1) || strncmp(s1+3,sentenceFormatterMnemonicCode,3) || strncmp(s1+6,",",1); + return (s1.size() > 6) && (s1.at(0) == '$') && (s1.at(6) == ',') && + (s1.mid(3,3) == sentenceFormatterMnemonicCode); } void -NmeaFormat::nmea_parse_one_line(char* ibuf) +NmeaFormat::nmea_parse_one_line(const QByteArray& ibuf) { - char* tbuf = lrtrim(ibuf); + QByteArray tbuf = ibuf.trimmed(); /* * GISTEQ PhotoTracker (stupidly) puts a bogus field in front * of the line. Look for it and toss it. */ - if (0 == strncmp(tbuf, "---,", 4)) { - tbuf += 4; + if (tbuf.startsWith("---,")) { + tbuf.remove(0, 4); } - if (*tbuf != '$') { + if (tbuf.at(0) != '$') { return; } - char* ck = strrchr(tbuf, '*'); - if (ck != nullptr) { - *ck = '\0'; - int ckval = nmea_cksum(&tbuf[1]); - *ck = '*'; - ck++; - int ckcmp; - sscanf(ck, "%2X", &ckcmp); - if (ckval != ckcmp) { - Warning().nospace() << hex << "Invalid NMEA checksum. Computed 0x" << ckval << " but found 0x" << ckcmp << ". Ignoring sentence."; + if (int ckidx = tbuf.lastIndexOf('*'); ckidx >= 0) { + bool checked = false; + if ((ckidx + 2) < tbuf.size()) { + QByteArray ckstring = tbuf.mid(ckidx + 1, 2); + bool ok; + int ckcmp = ckstring.toInt(&ok, 16); + if (ok) { + int ckval = nmea_cksum(tbuf.mid(1, ckidx - 1)); + if (ckval != ckcmp) { + Warning().nospace() << hex << "Invalid NMEA checksum. Computed 0x" << ckval << " but found 0x" << ckcmp << ". Ignoring sentence."; + return; + } + checked = true; + } + } + if (!checked) { + Warning().nospace() << "Unrecoverable NMEA checksum in line " << tbuf << ". Ignoring sentence."; return; } - - had_checksum = 1; + // hide checksum from sentence parsers. + tbuf.truncate(ckidx); + had_checksum = true; } else if (had_checksum) { /* we have had a checksum on all previous sentences, but not on this one, which probably indicates this line is truncated */ - had_checksum = 0; + had_checksum = false; return; } - if (strstr(tbuf+1,"$")!=nullptr) { + if (tbuf.count("$") > 1) { /* If line has more than one $, there is probably an error in it. */ return; } @@ -917,16 +906,14 @@ NmeaFormat::nmea_parse_one_line(char* ibuf) for that field. Rather than change all the parse routines, we first substitute a default value of zero for any missing field. */ - if (strstr(tbuf, ",,")) { - tbuf = gstrsub(tbuf, ",,", ",0,"); - } + tbuf.replace(",,", ",0,"); - if (0 == notalkerid_strmatch(tbuf, "WPL")) { + if (notalkerid_strmatch(tbuf, "WPL")) { gpwpl_parse(tbuf); - } else if (opt_gpgga && (0 == notalkerid_strmatch(tbuf, "GGA"))) { + } else if (opt_gpgga && notalkerid_strmatch(tbuf, "GGA")) { posn_type = gpgga; gpgga_parse(tbuf); - } else if (opt_gprmc && (0 == notalkerid_strmatch(tbuf, "RMC"))) { + } else if (opt_gprmc && notalkerid_strmatch(tbuf, "RMC")) { if (posn_type != gpgga) { posn_type = gprmc; } @@ -935,40 +922,35 @@ NmeaFormat::nmea_parse_one_line(char* ibuf) * it contains the full date. */ gprmc_parse(tbuf); - } else if (0 == notalkerid_strmatch(tbuf, "GLL")) { + } else if (notalkerid_strmatch(tbuf, "GLL")) { if ((posn_type != gpgga) && (posn_type != gprmc)) { gpgll_parse(tbuf); } - } else if (0 == notalkerid_strmatch(tbuf, "ZDA")) { + } else if (notalkerid_strmatch(tbuf, "ZDA")) { gpzda_parse(tbuf); - } else if (0 == strncmp(tbuf, "$PCMPT,", 7)) { - pcmpt_parse(tbuf); - } else if (opt_gpvtg && (0 == notalkerid_strmatch(tbuf, "VTG"))) { + } else if (tbuf.startsWith("$PCMPT,")) { + pcmpt_parse(tbuf.constData()); + } else if (opt_gpvtg && notalkerid_strmatch(tbuf, "VTG")) { gpvtg_parse(tbuf); /* speed and course */ - } else if (opt_gpgsa && (0 == notalkerid_strmatch(tbuf, "GSA"))) { + } else if (opt_gpgsa && notalkerid_strmatch(tbuf, "GSA")) { gpgsa_parse(tbuf); /* GPS fix */ - } else if (0 == strncmp(tbuf, "$ADPMB,5,0", 10)) { + } else if (tbuf.startsWith("$ADPMB,5,0")) { amod_waypoint = true; } - - if (tbuf != ibuf) { - /* clear up the dynamic buffer we used because substitution was required */ - xfree(tbuf); - } } void NmeaFormat::read() { char* ibuf; - double lt = -1; + QTime lt; int line = -1; posn_type = gp_unknown; trk_head = nullptr; without_date = 0; - memset(&tm, 0, sizeof(tm)); - opt_tm = tm; + prev_datetime = QDateTime(); + opt_tm = QDate(); /* This was done in rd_init() */ if (getposn) { @@ -976,13 +958,9 @@ NmeaFormat::read() } if (optdate) { - memset(&opt_tm, 0, sizeof(opt_tm)); - - char* ck = strptime(optdate, "%Y%m%d", &opt_tm); - if ((ck == nullptr) || (*ck != '\0') || (strlen(optdate) != 8)) { + opt_tm = QDate::fromString(optdate, "yyyyMMdd"); + if (!opt_tm.isValid()) { fatal(MYNAME ": Invalid date \"%s\"!\n", optdate); - } else if (opt_tm.tm_year < 70) { - fatal(MYNAME ": Date \"%s\" is out of range (have to be 19700101 or later)!\n", optdate); } } @@ -1108,7 +1086,7 @@ Waypoint* NmeaFormat::rd_position(posn_status* /*unused*/) { char ibuf[1024]; - static double lt = -1; + static QTime lt; int am_sirf = 0; /* @@ -1143,7 +1121,7 @@ NmeaFormat::rd_position(posn_status* /*unused*/) } nmea_parse_one_line(ibuf); if (lt != last_read_time) { - if (last_read_time) { + if (last_read_time.isValid()) { Waypoint* w = curr_waypt; lt = last_read_time; @@ -1182,16 +1160,16 @@ NmeaFormat::nmea_wayptpr(const Waypoint* wpt) const ); int cksum = nmea_cksum(obuf); gbfprintf(file_out, "$%s*%02X\n", obuf, cksum); - if (sleepus >= 0) { + if (sleepms >= 0) { gbfflush(file_out); - QThread::usleep(sleepus); + QThread::msleep(sleepms); } } void NmeaFormat::nmea_track_init(const route_head* /*unused*/) { - last_time = -1; + first_trkpt = true; } void @@ -1203,17 +1181,19 @@ NmeaFormat::nmea_trackpt_pr(const Waypoint* wpt) if (opt_sleep) { gbfflush(file_out); - if (last_time > 0) { - if (sleepus >= 0) { - QThread::usleep(sleepus); + if (!first_trkpt) { + if (sleepms >= 0) { + QThread::msleep(sleepms); } else { - long wait_time = wpt->GetCreationTime().toTime_t() - last_time; + // wait_time will be 0 if either creation time is invalid. + long wait_time = last_write_time.msecsTo(wpt->GetCreationTime()); if (wait_time > 0) { - QThread::usleep(wait_time * 1000000); + QThread::msleep(wait_time); } } } - last_time = wpt->GetCreationTime().toTime_t(); + last_write_time = wpt->GetCreationTime(); + first_trkpt = false; } double lat = degrees2ddmm(wpt->latitude); @@ -1389,6 +1369,6 @@ void NmeaFormat::reset_sirf_to_nmea(int br) pkt[27] = br & 0xff; sirf_write(pkt); - QThread::usleep(250 * 1000); + QThread::msleep(250); gbser_flush(gbser_handle); } diff --git a/nmea.h b/nmea.h index 98624269c..3da5acf60 100644 --- a/nmea.h +++ b/nmea.h @@ -22,15 +22,17 @@ #ifndef NMEA_H_INCLUDED_ #define NMEA_H_INCLUDED_ -#include // for gmtime - -#include // for QList -#include // for QString, QString::KeepEmptyParts -#include // for QVector +#include // for QByteArray +#include // for QDate +#include // for QDateTime +#include // for QList +#include // for QString +#include // for QTime +#include // for QVector #include "defs.h" -#include "format.h" -#include "gbfile.h" // for gbfprintf, gbfflush, gbfclose, gbfopen, gbfgetstr, gbfile +#include "format.h" // for Format +#include "gbfile.h" // for gbfile class NmeaFormat : public Format @@ -103,19 +105,20 @@ private: Waypoint* nmea_new_wpt(); void nmea_add_wpt(Waypoint* wpt, route_head* trk) const; static void nmea_release_wpt(Waypoint* wpt); - void nmea_set_waypoint_time(Waypoint* wpt, tm* time, double fsec); - void gpgll_parse(char* ibuf); - void gpgga_parse(char* ibuf); - void gprmc_parse(char* ibuf); - void gpwpl_parse(char* ibuf); - void gpzda_parse(char* ibuf); - void gpgsa_parse(char* ibuf) const; - void gpvtg_parse(char* ibuf) const; + void nmea_set_waypoint_time(Waypoint* wpt, QDateTime* prev, const QDate& date, const QTime& time); + static QTime nmea_parse_hms(const QString& str); + void gpgll_parse(const QString& ibuf); + void gpgga_parse(const QString& ibuf); + void gprmc_parse(const QString& ibuf); + void gpwpl_parse(const QString& ibuf); + void gpzda_parse(const QString& ibuf); + void gpgsa_parse(const QString& ibuf) const; + void gpvtg_parse(const QString& ibuf) const; static double pcmpt_deg(int d); - void pcmpt_parse(char* ibuf); + void pcmpt_parse(const char* ibuf); void nmea_fix_timestamps(route_head* track); - static int notalkerid_strmatch(const char* s1, const char* sentenceFormatterMnemonicCode); - void nmea_parse_one_line(char* ibuf); + static bool notalkerid_strmatch(const QByteArray& s1, const char* sentenceFormatterMnemonicCode); + void nmea_parse_one_line(const QByteArray& ibuf); static void safe_print(int cnt, const char* b); int hunt_sirf(); void nmea_wayptpr(const Waypoint* wpt) const; @@ -130,7 +133,7 @@ private: route_head* trk_head{}; short_handle mkshort_handle{}; preferred_posn_type posn_type; - struct tm tm{}; + QDateTime prev_datetime; Waypoint* curr_waypt{}; Waypoint* last_waypt{}; void* gbser_handle{}; @@ -138,7 +141,7 @@ private: QList pcmpt_head; int without_date{}; /* number of created trackpoints without a valid date */ - struct tm opt_tm{}; /* converted "date" parameter */ + QDate opt_tm; /* converted "date" parameter */ char* opt_gprmc{}; char* opt_gpgga{}; @@ -153,18 +156,16 @@ private: char* opt_gisteq{}; char* opt_ignorefix{}; - long sleepus{}; + long sleepms{}; int getposn{}; int append_output{}; bool amod_waypoint{}; - time_t last_time{}; - double last_read_time{}; /* Last timestamp of GGA or PRMC */ + QDateTime last_write_time; + bool first_trkpt{}; + QTime last_read_time; /* Last timestamp of GGL, GGA or RMC */ int datum{}; - int had_checksum{}; - - Waypoint* nmea_rd_posn(posn_status*); - void nmea_rd_posn_init(const QString& fname); + bool had_checksum{}; int wpt_not_added_yet{}; diff --git a/reference/track/addmultipledays.csv b/reference/track/addmultipledays.csv new file mode 100644 index 000000000..bc29dcba7 --- /dev/null +++ b/reference/track/addmultipledays.csv @@ -0,0 +1,29 @@ +No,Latitude,Longitude,Altitude,FIX,HDOP,Satellites,Date,Time +1,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/08,22:00:00 +2,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/08,23:00:00 +3,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,00:00:00 +4,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,01:00:00 +5,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,02:00:00 +6,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,03:00:00 +7,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,04:00:00 +8,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,05:00:00 +9,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,06:00:00 +10,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,07:00:00 +11,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,08:00:00 +12,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,09:00:00 +13,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,10:00:00 +14,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,11:00:00 +15,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,12:00:00 +16,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,13:00:00 +17,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,14:00:00 +18,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,15:00:00 +19,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,16:00:00 +20,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,17:00:00 +21,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,18:00:00 +22,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,19:00:00 +23,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,20:00:00 +24,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,21:00:00 +25,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,22:00:00 +26,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/09,23:00:00 +27,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/10,00:00:00 +28,50.224600,17.174617,468.8,"3d",0.80,9,2019/03/10,01:00:00 diff --git a/reference/track/addmultipledaysbkw.nmea b/reference/track/addmultipledaysbkw.nmea new file mode 100644 index 000000000..83c06a7f4 --- /dev/null +++ b/reference/track/addmultipledaysbkw.nmea @@ -0,0 +1,29 @@ +$GPGGA,220000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,230000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,000000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,010000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,020000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,030000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,040000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*62 +$GPGGA,050000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*63 +$GPGGA,060000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*60 +$GPGGA,070000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*61 +$GPGGA,080000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6e +$GPGGA,090000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6f +$GPGGA,100000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,110000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,120000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,130000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,140000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*63 +$GPGGA,150000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*62 +$GPGGA,160000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*61 +$GPGGA,170000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*60 +$GPGGA,180000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6f +$GPGGA,190000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6e +$GPGGA,200000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,210000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,220000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,230000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,000000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,010000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPZDA,033141,10,03,2019,00,00*44 diff --git a/reference/track/addmultipledaysfwd.nmea b/reference/track/addmultipledaysfwd.nmea new file mode 100644 index 000000000..25e3081a6 --- /dev/null +++ b/reference/track/addmultipledaysfwd.nmea @@ -0,0 +1,28 @@ +$GPGGA,220000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,230000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,000000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,010000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,020000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,030000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,040000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*62 +$GPGGA,050000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*63 +$GPGGA,060000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*60 +$GPGGA,070000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*61 +$GPGGA,080000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6e +$GPGGA,090000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6f +$GPGGA,100000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,110000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,120000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,130000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,140000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*63 +$GPGGA,150000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*62 +$GPGGA,160000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*61 +$GPGGA,170000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*60 +$GPGGA,180000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6f +$GPGGA,190000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6e +$GPGGA,200000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,210000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,220000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,230000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,000000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,010000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 diff --git a/reference/track/addmultipledaysmid.nmea b/reference/track/addmultipledaysmid.nmea new file mode 100644 index 000000000..61d1469c2 --- /dev/null +++ b/reference/track/addmultipledaysmid.nmea @@ -0,0 +1,29 @@ +$GPGGA,220000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,230000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,000000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,010000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,020000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,030000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,040000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*62 +$GPGGA,050000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*63 +$GPGGA,060000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*60 +$GPGGA,070000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*61 +$GPGGA,080000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6e +$GPGGA,090000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6f +$GPGGA,100000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPZDA,103000,09,03,2019,00,00*4a +$GPGGA,110000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,120000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,130000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,140000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*63 +$GPGGA,150000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*62 +$GPGGA,160000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*61 +$GPGGA,170000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*60 +$GPGGA,180000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6f +$GPGGA,190000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*6e +$GPGGA,200000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*64 +$GPGGA,210000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*65 +$GPGGA,220000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,230000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 +$GPGGA,000000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*66 +$GPGGA,010000.000,5013.476,N,01710.477,E,1,09,0.8,468.770,M,0.0,M,,*67 diff --git a/testo.d/nmea.test b/testo.d/nmea.test index 3ef9e647f..785de5890 100644 --- a/testo.d/nmea.test +++ b/testo.d/nmea.test @@ -31,3 +31,11 @@ compare ${REFERENCE}/track/backfilldate2.csv ${TMPDIR}/backfilldate2.csv gpsbabel -i gpx -f ${REFERENCE}/track/nmeadate.gpx -o nmea -F ${TMPDIR}/nmeadate.nmea compare ${REFERENCE}/track/nmeadate.nmea ${TMPDIR}/nmeadate.nmea + + +gpsbabel -t -i nmea,date=20190308 -f ${REFERENCE}/track/addmultipledaysfwd.nmea -o unicsv,utc=0 -F ${TMPDIR}/addmultipledaysfwd.csv +compare ${REFERENCE}/track/addmultipledays.csv ${TMPDIR}/addmultipledaysfwd.csv +gpsbabel -t -i nmea -f ${REFERENCE}/track/addmultipledaysbkw.nmea -o unicsv,utc=0 -F ${TMPDIR}/addmultipledaysbkw.csv +compare ${REFERENCE}/track/addmultipledays.csv ${TMPDIR}/addmultipledaysbkw.csv +gpsbabel -t -i nmea -f ${REFERENCE}/track/addmultipledaysmid.nmea -o unicsv,utc=0 -F ${TMPDIR}/addmultipledaysmid.csv +compare ${REFERENCE}/track/addmultipledays.csv ${TMPDIR}/addmultipledaysmid.csv diff --git a/xmldoc/formats/options/nmea-pause.xml b/xmldoc/formats/options/nmea-pause.xml index 72e42ec73..88112a5db 100644 --- a/xmldoc/formats/options/nmea-pause.xml +++ b/xmldoc/formats/options/nmea-pause.xml @@ -10,7 +10,7 @@ either a whole number of seconds or a fraction (e.g. 0.5 for a 1/2 second pause between trackpoints.) -If this option is specified without a value, the time between adjacent +If this option is specified with a negative value, the time between adjacent trackpoints will be computed and used for the length of the pause. That is, if your trackpoints are 5 seconds apart, GPSBabel will pause 5 seconds between trackpoints. -- 2.30.2